rJ|jsC| dur?|}|rd}nt%|d dpd}t'|| p| |t)||jt jkpt-|  S#t.$r|wxY @;@7o{Bo06DomCoP1BoPoCEo{BopLUg}d}d}|D].}|d\}}d}d} i} i} |ddD]\} } | }||vs||vr|} | |vr| d} | | vr0| dkr)| td d} n| } | d kr|rh| td z| d krAd} t| } n"#t$rtd d} Yn@wxYwd } |j| z} | |vs| |vr"| | dvrtd| zd} n | | | <| | | <| r|||| | f0|SOoAJLULU0xr`ǁr0PoPopPoOoQrPoҐo0OoOoOo0LU@>>+++ D#y    ("g--#'#:#:49d#K#KK D! ,0D ( LLNNN"/O   +"2Om))++k9   M6k666"    @    nn (( !% 8 8  !% 8 8  ]]  (( ** nn ]] ** ,O (( "z !00 $66! $&, ,8 (,DJ'7'7$$$IPo̎o(3aoovoHrOo@uLUr|x|_}|j}d}|jQ|jdkr$||j|x|_}d}d|_||j}|j|j}tj tk}|r#tjdtdtt!did|d|jd|jd |jd |jd |jd |jd |jd|jd|jd|jd|d|jd|d|jd|j}|j|_|j|_tA|j!|_d@s`Fo|LU}LUP~LU8~LU3`d~LUV LUGcpb4~LU׀tQoP!И@KzPo0Fo0Fo`(LUMMpLUP(LU mixin for classes who implement a :meth:`urlopen` method, such as :class:`urllib3.HTTPConnectionPool` and :class:`urllib3.PoolManager`. Provides behavior for making common types of HTTP request methods and decides which type of request field encoding to use. Specifically, :meth:`.request_encode_url` is for sending requests whose fields are encoded in the URL (such as GET, HEAD, DELETE). :meth:`.request_encode_body` is for sending requests whose fields are encoded in the *body* of the request using multipart or www-form-urlencoded (such as for POST, PUT, PATCH). :meth:`.request` is for making any kind of request, it will look up the appropriate encoding format and use one of the above two methods to make the request. Initializer parameters: :param headers: Headers to include with all requests, unless other headers are given explicitlPeF MUeFZJeGddeFZKGdd eFZLd+d$ZMGd%d&ZNGd'd(ejOZPGd)d*eP0@H NU߿E=bڠ݀ppOoOo@GPVLULU֍o(3 8 QoвovoPoOoQontjdtd|#|j |jj}nt d}||_||_||_||_ ||_ ||_ |otj ||_|otj ||_| |_dS|Py | $B **,, *P   " ") 0) 0& +>t?V+W+W '--/// ) 0) 0) 0) 0) 0) 0) 0) 0x{") 0) 0) 0) 0) 0) 0) 0) 0 "))+++?a''**3//,,.'"!MMM " "--//"!MMM(&x{++--7?? (&& 0'--///S) 0) 0) 0) 0) 0) 0) 0) 0) 0) 0) 0) 0) 0) 0) 0) 0) 0) 00o`Oo(3#aOoPo0qo{OopoKg}|dD]|}d|vra|d}d||dzdzV|ddD] }|dzV |dr |dg}dg}g||}|rd|VdSdS1psLUpMU30nGo&Eo(3vOo0LohGo(yOoDo ||_|td}|dkr |d|}tjdkr.tj|vr |tjd}||_||_|ddkrtdt|_ d|_ d|_ d|_ tjdkrd|_nd|_t"|_t"|_d|_d|_d|_d|_d|_d|_d|_d|_dS` P + 0 &H @H @L&q/KD% KJHD$QRR(2 2 1WWYY$$m(;(;A %%!)A====yFGGG%)  A >>"! y FGGG  >>"&KFF% 2333%)  "A A A$$!}*<*< !BBBBQFGGG%) "#HQKKDGG   $x!> ? ? ? ?ZSA; MU  MU>p(3    ooQo(ypZQoot|tr|jd|jd|j} n|} t | } | jdvrt | j| js0tj| jd} | | } | |_ |pi|_ ||_ t|||||_|j | d<|j | d<|j| d<tj||fi| G  %'+C">#9;/D2 < (-.,!7*=3:FEA$814 @&)?5 6B00worhpOo`}OoЀPortoDom@JrSr0xr0Kxrppr`jrk@߀P6r|rP1rlqzr0PomLUpTp`NLUĜp8LU0yo׀܉oLUo2oƊoooo؉oLUpo*opoopqʼnp0Do&LU0oPooLUȊoLUpZr0Aoo.LUoPLU@oP6LUo2LU0o0LU0ohLUpoo@ӊoRopdpՊooPIoo@-o`Ooތo0ԊoopooԊooԊo oo o؉p`LUpp/opoLU0 ؀0׀prЏPoLoPo0PooOoToPo0(pPLU0p LUOoLUPo@(o0PoQoQoQoOo QoFoLU`Oo LUOoQopzrP>LULU-o(3 }oo:opp/Ipg}|sdS|d}|d}|dd}|d}||krtdt|z|s7t ||kS|dkr|dn|d s|d r(|tj |n;|tj | d d |D])}|tj |*tj d d |zdztj } | |SP@soxLULUЦLULU3-Ā`d LUVȎoGcpb4hLU׀toP!И@Kz`oooLU @sPoHLUhLULULU3-Ā`dLUVpLUGcpb48LULU@PoP!И@Kz@o o oLUddl"m#!0LUtLU for one host. :param host: Host used for this HTTP Connection (e.g. "localhost"), passed into :class:`http.client.HTTPConnection`. :param port: Port used for this HTTP Connection (None is equivalent to 80), passed into :class:`http.client.HTTPConnection`. :param timeout: Socket timeout in seconds for each individual connection. This can be a float or integer, which sets the timeout for the HTTP request, or an instance of :class:`urllib3.util.Timeout` which gives you more fine-grained control over request timeouts. After the constructor has been parsed, this is always a `urllib3.util.Timeout` object. :param maxsize: Number of connections to save that can be reused. More than 1 is useful in multithreaded situations. If ``block`` is set to False, more connections will be created but they will not be saved once they've been used. :param block: If set to True, no more than ``maxsize`` connections will be used at a time. When no free connections are available, the call will block until a connection has been released. This is a useful side effect for particular multithreaded situations where one does not want to use more than maxsize connections per host to prevent flooding. :param headers: Headers to include with all requests, unless other headers are given explicitly. :param retries: Retry configuration to use by default with requests in this pool. :param _proxy: Parsed proxy URL, should not be used directly, instead, see :class:`urllib3.ProxyManager` :param _proxy_headers: A dictionary with proxy headers, should not be used directly, instead, see :class:`urllib3.ProxyManager` :param \**conn_kw: Additional parameters are used to create fresh :class:`urllib3.connection.HTTPConnection`, :class:`urllib3.connection.HTTPSConnection` instances. @so8LUXLULUxLU3-Ā`dLUV`>LUGcpb4(LULUPoP!И@KzoДoДo?LU HTTP Cookie. This class represents both Netscape and RFC 2965 cookies. This is deliberately a very simple class. It just holds attributes. It's possible to construct Cookie instances that don't comply with the cookie standards. CookieJar.make_cookies is the factory function for Cookie objects -- it deals with cookie parsing, supplying defaults, and normalising to the representation used in this class. CookiePolicy is responsible for checking them to see whether they should be accepted from and returned to the server. Note that the port may be present in the headers, but unspecified ("Port" rather than"Port=80", for example); if this is the case, port is None. qLU+MU   ѭrrpySor0Qopr@0r rHorToέrTo0ϭrgort0'rpxSoЦSpsSoTosSoToxrF0~LU !MUpOQoPoOQo PQo @LULU|d D]3} | | jvr| | d4| |d < | ||||} n.#t2$r!| jr||cYSwxYw| |d <||d<t8d||||j ||fi| +o]oPo  0ooQopqWQoPLUt|}|jtjdtd||j|j|j}d|d<d|d<d |vr |j|d <| |r|j ||fi|}n|j ||j fi|}|o| }|s|St||}|jd krd }|d } t!| t"st#j| | } | jrp||s[|d } |d D]3} | | jvr| | d4| |d < | ||||} n.#t2$r!| jr||cYSwxYw| |d <||d<t8d||||j ||fi|SJLU LU     pKoGRo@oGRopPo HRoPoHRoPoHRopPo@IRo }o@RoPoJRo|oIRo0PoJRopPo KRoPoKRoPoKRo0JQo`JRoJQo@LRo0PoMRopPo`MRoPoMRoPo NRopPoNRo KQoLRoKQoNRo`LQo@ORoLQoORoPMQoPRoMQo`PRoMQoPRoPoQRo @soLU(LU`LUHLU3`dLUV@~LUGcpb4LU`{LUKSoP!И@Kz SoooPLULU !MU@s oLULU0LULU3`dLUVLUGcpb4LUpLU@KSoP!И@Kz )SoooLUGp/opt/alt/python-internal/lib64/python3.11/lib-dynload/_multibytecodec.cpython-311-x86_64-linux-gnu.soLUPVLUF(h((((((((((((HHHHHHHHHH(((((((((((`5o6o(3 Po0BoPo2Do`BEoAoj|ddd}g}|D]|}|j}|s d}|dkr|d|z|jF|j|jr'|dkr!|jd|j}n|j}|j||jn||jd ||dkr|j r|d |j z|j d rE|j }|j s| d r |d d}|d |z|j+d}|jr |d|jzz}||~|Sdt1ʿLU+MUZded1 LULUCreate new instance of PoolKey(key_scheme, key_host, key_port, key_timeout, key_retries, key_block, key_source_address, key_key_file, key_key_password, key_cert_file, key_cert_reqs, key_ca_certs, key_ssl_version, key_ssl_minimum_version, key_ssl_maximum_version, key_ca_cert_dir, key_ssl_context, key_maxsize, key_headers, key__proxy, key__proxy_headers, key__proxy_config, key_socket_options, key__socks_options, key_assert_hostname, key_assert_fingerprint, key_server_hostname, key_blocksize)@p[<7K^ (\d{4}) # year [-\/]? (\d\d?) # numerical month [-\/]? (\d\d?) # day (?: (?:\s+|[-:Tt]) # separator before clock (\d\d?):?(\d\d) # hour:min (?::?(\d\d(?:\.\d*)?))? # optional seconds (and fractional) )? # optional clock \s* (?: ([-+]?\d\d?:?(:?\d\d)? |Z|z) # timezone (Z is "zero meridian", i.e. GMT) \s* Prr0rLU!LU*%!#$ )'   ( " &r0oȊrporESox SorSo0r@QoXQorQo0 QopopKop So@o` SopPo SoPo@SoPopSopPoSo }oSoPoSo|o0So0Po`SopPoSoPoSoPoSo0JQo SoJQoPSo0PoSopPoSoPoSoPoSopPo@'So KQop'SoKQo'So`LQo'SoLQo(SoPMQo0(SoMQo`(SoMQo(SoPo(SoɊoHl@Popr0o9-oui u -o.-ou -oLULUwTo~Io 4 Bop~Io0Ho0 Lo0 LoPJFoddlm}d}t|D]\}}t|} ||dkr|ntj||dfdz }|d} d} t |dkr|d} | d ks| d kr|r | d kr |s| ||z }n2| | d ks| d kr|r| d kr|r|| z }n| d krt #t $rBtdt||dzt|wxYwtj d|SQLULUoolManager`, but sends all requests through the defined proxy, using the CONNECT method for HTTPS URLs. :param proxy_url: The URL of the proxy to be used. :param proxy_headers: A dictionary containing headers that will be sent to the proxy. In case of HTTP they are being sent with each request, while in the HTTPS/CONNECT case they are sent only once. Could be used for proxy authentication. :param proxy_ssl_context: The proxy SSL context is used to establish the TLS connection to the proxy when using HTTPS proxies. :param use_forwarding_for_https: (Defaults to False) If set to True will forward requests to the HTTPS proxy to be made on behalf of the client instead of creating a TLS tunnel via the CONNECT method. **Enabling this flag means that request and response headers and content will be visible from the HTTPS proxy** whereas tunneling keeps request and response headers and content private. IP address, target hostname, SNI, and port are always visible to an HTTPS proxy even when this flag is disabled. :param proxy_assert_hostname: The hostname of the certificate to verify against. :param proxy_assert_fingerprint: The fingerprint of the certificate to verify against. Example: .. code-block:: python import urllib3 proxy = urllib3.ProxyManager("https://localhost:3128/") resp1 = proxy.request("GET", "https://google.com/") resp2 = proxy.request("GET", "https://httpbin.org/") print(len(proxy.pools)) # 1 resp3 = proxy.request("GET", "https://httpbin.org/") resp4 = proxy.request("GET", "https://twitter.com/") print(len(proxy.pools)) # 3  LULU) +"# &!  '% *($ ɊorhpKVp`}IoPor0soDom@JrSr݀0Яrppr`jrqzr0+rEp|pɊo@LU0Fo`{LUoLU oLUДoLULoPooELUoLU0oLUo0MLUo.LUNJooQo QootLU؉oLUpoLUo݌ok?oprPoPoeoNQoRooPPoGo MUNQoQo PQo@PoOQoPoopLUo@LU0oQoool CMU LU 1*" +$'(0% -)  #,&2./!pډorPohpډo`}߉oo@.LprOoD~om@JrSrppr`jrqzr0o݀ӊo0oŊo0oPopKrΊo܉oLU0 rPopZr0AoyrJopo GopdpOop@rofo~o0opQo oLUДoLUpo`&o߉oPOoooogoopLUo@LU0oQoo.LUvpp5LUo@(ootLU؉oLUpqʼnp|ׄop9qpdqk0or`o@7oo`MooPao0qQo(o@(opHSo;dw0SpSR|)C0ISoBSApPoV NpHSoj)r!}!hnHSo!'+pSp(Km: 0o>ȇe0Po@_Th0HSoBzpGSoNq;.pISoO]jV GSoŭ16vGSo n*ESoz4|o eMUЌMU5:H6 s ! ! ! !SZZ\ ! ! ! ! !5q>""f,,U37^1D1D1N1Ntu V   UA  #e**q..eC!Gng66 6u V  x611 77eC!Gnh77 7u V    BX~~"j)) ZJ-G-G :VXZ_K`K` tt u ( # # # #e # # # # #  BB))))6)))))uu*t ( # # # #e # # # # #  BB))))6)))))uu*t 5 MU` MU>o(3U|Qo$So`MU>MU @MU@MU0n-Ā`dp@MUVPoGcpb4@MU`ELUToP!И2zSorn7Ā8Āock']; $query = $args['query']; $client_id = $args['clientId']; $raw_context = $args['context']; $post_id = $args['post_id']; // Bail early if no block. if ( ! $block ) { wp_send_json_error(); } // Unslash and decode $_POST data for block and context. $block = wp_unslash( $block ); $block = json_decode( $block, true ); $context = false; if ( ! empty( $raw_context ) ) { $raw_context = wp_unslash( $raw_context ); $raw_context = json_decode( $raw_context, true ); if ( is_array( $raw_context ) ) { $context = $raw_context; // Check if a postId is set in the context, and verify the user has access to that post. if ( isset( $context['postId'] ) ) { if ( ! acf_current_user_can_edit_post( intval( $context['postId'] ) ) ) { wp_send_json_error(); } $post_id = intval( $context['postId'] ); } else { $post_id = intval( $args['post_id'] ); } } } // Check if clientId should become $block['id']. if ( empty( $block['id'] ) && ! empty( $client_id ) ) { $block['id'] = $client_id; } // Prepare block ensuring all settings and attributes exist. $block = acf_prepare_block( $block ); $block = acf_add_block_meta_values( $block, $post_id ); if ( ! $block ) { wp_send_json_error(); } // Load field defaults when first previewing a block. $first_preview = false; if ( ! empty( $query['preview'] ) && ! $block['data'] ) { $fields = acf_get_block_fields( $block ); foreach ( $fields as $field ) { $block['data'][ "_{$field['name']}" ] = $field['key']; } $first_preview = true; } // Setup postdata allowing form to load meta. acf_setup_meta( $block['data'], $block['id'], true ); // Setup main postdata for post_id. global $post; //phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- required for block template rendering. $post = get_post( $post_id ); setup_postdata( $post ); // Vars. $response = array( 'clientId' => $client_id ); // Check if we've recieved serialised form data $use_post_data = false; if ( ! empty( $block['data'] ) && is_array( $block['data'] ) ) { // Ensure we've got field keys posted. $valid_field_keys = array_filter( array_keys( $block['data'] ), 'acf_is_field_key' ); if ( ! empty( $valid_field_keys ) ) { $use_post_data = true; } } $query['validate'] = ( ! empty( $query['validate'] ) && ( $query['validate'] === 'true' || $query['validate'] === true ) ); if ( ! empty( $query['validate'] ) || ! empty( $block['validate'] ) ) { $response['validation'] = acf_get_block_validation_state( $block, $first_preview, $use_post_data ); } // Query form. if ( ! empty( $query['form'] ) ) { // Load fields for form. $fields = acf_get_block_fields( $block ); // Prefix field inputs to avoid multiple blocks using the same name/id attributes. acf_prefix_fields( $fields, "acf-{$block['id']}" ); if ( $fields ) { $response['fields'] = $fields; // Start Capture. ob_start(); // Render. echo '
'; acf_render_fields( $fields, $block['id'], 'div', 'field' ); echo '
'; // Store Capture. $response['form'] = ob_get_clean(); } else { // There are no fields on this block. $response['form'] = acf_get_empty_block_form_html( $block['name'] ); //phpcs:ignore -- escaped in function. } } // Query preview. if ( ! empty( $query['preview'] ) ) { // Render_callback vars. $content = ''; $is_preview = true; // Render and store HTML. $response['preview'] = acf_rendered_block( $block, $content, $is_preview, $post_id, null, $context, true ); $response['blockToolbarFields'] = acf_process_block_toolbar_fields( apply_filters( 'acf/blocks/top_toolbar_fields', array(), $block, $content, $is_preview, $post_id, null, $context ) ); } // Send response. wp_send_json_success( $response ); } // Register ajax action. acf_register_ajax( 'fetch-block', 'acf_ajax_fetch_block' ); /** * Render the empty block form for when a block has no fields assigned. * * @since 6.0.0 * * @param string $block_name The block name current being rendered. * @return string The html that makes up a block form with no fields. */ function acf_get_empty_block_form_html( $block_name ) { $message = __( 'This block contains no editable fields.', 'acf' ); if ( acf_current_user_can_admin() ) { $message .= ' '; $message .= sprintf( /* translators: %s: an admin URL to the field group edit screen */ __( 'Assign a field group to add fields to this block.', 'acf' ), admin_url( 'edit.php?post_type=acf-field-group' ) ); } $message = apply_filters( 'acf/blocks/no_fields_assigned_message', $message, $block_name ); if ( ! is_string( $message ) ) { $message = ''; } if ( empty( $message ) ) { return acf_esc_html( '
' ); } else { return acf_esc_html( '
' . $message . '
' ); } } /** * Parse content that may contain HTML block comments and saves ACF block meta. * * @since 5.7.13 * * @param string $text Content that may contain HTML block comments. * @return string */ function acf_parse_save_blocks( $text = '' ) { // Search text for dynamic blocks and modify attrs. return addslashes( preg_replace_callback( '//', 'acf_parse_save_blocks_callback', stripslashes( $text ) ) ); } // Hook into saving process. add_filter( 'content_save_pre', 'acf_parse_save_blocks', 5, 1 ); /** * Callback used in preg_replace to modify ACF Block comment. * * @since 5.7.13 * * @param array $matches The preg matches. * @return string */ function acf_parse_save_blocks_callback( $matches ) { // Defaults. $name = isset( $matches['name'] ) ? $matches['name'] : ''; $attrs = isset( $matches['attrs'] ) ? json_decode( $matches['attrs'], true ) : ''; $void = isset( $matches['void'] ) ? $matches['void'] : ''; // Bail early if missing data or not an ACF Block. if ( ! $name || ! $attrs || ! acf_has_block_type( $name ) ) { return $matches[0]; } // Check if we need to generate a block ID. $block_id = acf_ensure_block_id_prefix( acf_get_block_id( $attrs ) ); if ( ! empty( $attrs['data'] ) ) { if ( acf_block_uses_post_meta( $attrs ) ) { // Block ID is used later to retrieve & save values. $attrs['id'] = $block_id; // Cache the values until we have a post ID and can save. $store = acf_get_store( 'block-meta-values' ); $store->set( $block_id, $attrs['data'] ); // No need to store values in post content. unset( $attrs['data'] ); } else { // Convert "data" to "meta". // No need to check if already in meta format. Local Meta will do this for us. $attrs['data'] = acf_setup_meta( $attrs['data'], $block_id ); } } /** * Filters the block attributes before saving. * * @since 5.7.14 * * @param array $attrs The block attributes. */ $attrs = apply_filters( 'acf/pre_save_block', $attrs ); // Gutenberg expects a specific encoding format. $attrs = acf_serialize_block_attributes( $attrs ); return ''; } /** * Return or generate a block ID. * * @since 6.0.0 * * @param array $attributes A block attributes array. * @param array $context The block context array, defaults to an empty array. * @param boolean $force If we should generate a new block ID even if one exists. * @return string A block ID. */ function acf_get_block_id( $attributes, $context = array(), $force = false ) { $context = is_array( $context ) ? $context : array(); ksort( $context ); $attributes['_acf_context'] = $context; if ( empty( $attributes['id'] ) || $force ) { unset( $attributes['id'] ); // Remove all empty string values as they're not present in JS hash building. foreach ( $attributes as $key => $value ) { if ( '' === $value ) { unset( $attributes[ $key ] ); } } // Check if data is empty and remove it if so to match JS hash building. if ( isset( $attributes['data'] ) && empty( $attributes['data'] ) ) { unset( $attributes['data'] ); } ksort( $attributes ); return md5( wp_json_encode( $attributes, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) ); } return $attributes['id']; } /** * Ensure a block ID always has a block_ prefix for post meta internals. * * @since 6.0.0 * * @param string $block_id A possibly non-prefixed block ID. * @return string A prefixed block ID. */ function acf_ensure_block_id_prefix( $block_id ) { if ( substr( $block_id, 0, 6 ) === 'block_' ) { return $block_id; } return 'block_' . $block_id; } /** * This directly copied from the WordPress core `serialize_block_attributes()` function. * * We need this in order to make sure that block attributes are stored in a way that is * consistent with how Gutenberg sends them over from JS, and so that things like wp_kses() * work as expected. Copied from core to get around a bug that was fixed in 5.8.1 or on the off chance * that folks are still using WP 5.3 or below. * * TODO: Remove this when we refactor `acf_parse_save_blocks_callback()` to use `serialize_block()`, * or when we're confident that folks aren't using WP versions prior to 5.8. * * @since 5.12 * * @param array $block_attributes Attributes object. * @return string Serialized attributes. */ function acf_serialize_block_attributes( $block_attributes ) { $encoded_attributes = wp_json_encode( $block_attributes, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); $encoded_attributes = preg_replace( '/--/', '\\u002d\\u002d', $encoded_attributes ); $encoded_attributes = preg_replace( '//', '\\u003e', $encoded_attributes ); $encoded_attributes = preg_replace( '/&/', '\\u0026', $encoded_attributes ); // Regex: /\\"/. $encoded_attributes = preg_replace( '/\\\\"/', '\\u0022', $encoded_attributes ); return $encoded_attributes; } /** * Handle validating a block's fields and return the validity, and any errors. * * This function can use values loaded into Local Meta, which means they have to be * converted back to the data format before they can be validated. * * @since 6.3 * * @param array $block An array of the block's data attribute. * @param boolean $using_defaults True if the block is currently being generated with default values. Default false. * @param boolean $use_post_data True if we should validate the POSTed data rather than local meta values. Default false. * @param boolean $on_load True if we're validating as part of a render. This is essentially the same as a first load. Default false. * @return array An array containing a valid boolean, and an errors array. */ function acf_get_block_validation_state( $block, $using_defaults = false, $use_post_data = false, $on_load = false ) { $block_id = $block['id']; if ( $on_load && empty( $block['validate_on_load'] ) ) { // If we're in a page load render, and validate on load is false, skip validation. $errors = false; } elseif ( $use_post_data ) { $errors = acf_validate_block_from_post_data( $block ); } elseif ( $using_defaults || empty( $block['data'] ) ) { // If data is empty or it's first preview, load the default fields for this block so we can get a required validation state from the current field set. // Treat as "on load" if it's the first render of a block. if ( empty( $block['validate_on_load'] ) ) { $errors = false; } else { $errors = acf_validate_block_from_local_meta( $block_id, acf_get_block_fields( $block ), true ); } } else { $errors = acf_validate_block_from_local_meta( $block_id, get_field_objects( $block_id, false ), false ); } return array( 'valid' => empty( $errors ), 'errors' => $errors, ); } /** * Handle the specific validation for a block from POSTed values. * * @since 6.3.1 * * @param array $block The block object containing the POSTed values and other block data * @return array|boolean An array containing the validation errors, or false if there are no errors. */ function acf_validate_block_from_post_data( $block ) { acf_reset_validation_errors(); acf_validate_values( $block['data'], "acf-{$block['id']}" ); $errors = acf_get_validation_errors(); return $errors; } /** * Handle the specific validation for a block from local meta. * * This function uses the values loaded into Local Meta, which means they have to be * converted back to the data format because they can be validated. * * @since 6.3.1 * * @param string $block_id The block ID. * @param array $field_objects The field objects in local meta to be validated. * @param boolean $using_defaults True if this is the first load of the block, when special validation may apply. * @return array|boolean An array containing the validation errors, or false if there are no errors. */ function acf_validate_block_from_local_meta( $block_id, $field_objects, $using_defaults = false ) { if ( empty( $field_objects ) ) { return false; } $using_loaded_meta = false; if ( acf_get_data( $block_id . '_loaded_meta_values' ) ) { $using_loaded_meta = true; } acf_reset_validation_errors(); foreach ( $field_objects as $field ) { // Skip for nested fields - these don't work correctly on initial load of a saved block. if ( ! empty( $field['sub_fields'] ) ) { continue; } // If we're using default values, or loaded meta we may have values which are about to be populated at field render, so shouldn't raise errors here. if ( $using_defaults || $using_loaded_meta ) { // Fields with conditional logic applied shouldn't be validated during first load as conditionals aren't respected. if ( ! empty( $field['conditional_logic'] ) ) { continue; } // If we've got a empty value with a default value set and it's first load, don't produce a validation error as it will be substituted on render. if ( $field['required'] && empty( $field['value'] ) && ! empty( $field['default_value'] ) ) { continue; } // If we're loading a few radio or select-like fields, without allow null, HTML will automatically select the first value on render, so skip here. if ( $field['required'] && in_array( $field['type'], array( 'radio', 'button_group', 'select' ), true ) && ! $field['allow_null'] ) { continue; } } $key = $field['key']; $value = $field['value']; acf_validate_value( $value, $field, "acf-{$block_id}[{$key}]" ); } return acf_get_validation_errors(); } /** * Set ACF data before a rest call if media scripts have not been enqueued yet for after REST reset. * * @date 07/06/22 * @since 6.0 * * @param WP_REST_Response|WP_HTTP_Response|WP_Error|mixed $response The WordPress response object. * @return mixed */ function acf_set_after_rest_media_enqueue_reset_flag( $response ) { global $wp_actions; acf_set_data( 'acf_inside_rest_call', true ); acf_set_data( 'acf_should_reset_media_enqueue', empty( $wp_actions['wp_enqueue_media'] ) ); acf_set_data( 'acf_did_render_block_form', false ); return $response; } add_filter( 'rest_request_before_callbacks', 'acf_set_after_rest_media_enqueue_reset_flag' ); /** * Reset wp_enqueue_media action count after REST call so it can happen inside the main execution if required. * * @date 07/06/22 * @since 6.0 * * @param WP_REST_Response|WP_HTTP_Response|WP_Error|mixed $response The WordPress response object. * @return mixed */ function acf_reset_media_enqueue_after_rest( $response ) { acf_set_data( 'acf_inside_rest_call', false ); if ( acf_get_data( 'acf_should_reset_media_enqueue' ) && acf_get_data( 'acf_did_render_block_form' ) ) { global $wp_actions; //phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- no other option here as this works around a breaking WordPress change with REST preload scopes. $wp_actions['wp_enqueue_media'] = 0; } return $response; } add_filter( 'rest_request_after_callbacks', 'acf_reset_media_enqueue_after_rest' ); /** * Checks if the provided block is configured to save/load post meta. * * @since 6.3 * * @param array $block The block to check. * @return boolean */ function acf_block_uses_post_meta( $block ): bool { if ( ! empty( $block['name'] ) && ! isset( $block['use_post_meta'] ) ) { $block = acf_get_block_type( $block['name'] ); } return ! empty( $block['use_post_meta'] ); } /** * Loads ACF field values from the post meta if the block is configured to do so. * * @since 6.3 * * @param array $block The block to get values for. * @param integer $post_id The ID of the post to retrieve meta from. * @return array */ function acf_add_block_meta_values( $block, $post_id ) { // Bail if the block already has data (i.e. previewing an update). if ( ! is_array( $block ) || ! empty( $block['data'] ) ) { return $block; } // Bail if block doesn't load from meta. if ( ! acf_block_uses_post_meta( $block ) ) { return $block; } // Bail if we don't have a post ID or block ID. if ( empty( $post_id ) || empty( $block['id'] ) ) { return $block; } $fields = acf_get_block_fields( $block ); if ( empty( $fields ) ) { return $block; } $values = array(); $store = acf_get_store( 'values' ); $block_id = acf_ensure_block_id_prefix( $block['id'] ); foreach ( $fields as $field ) { $value = acf_get_value( $post_id, $field ); // Make sure we got a value (i.e. $allow_load = true). if ( ! $store->has( "{$post_id}:{$field['name']}" ) ) { continue; } $store->set( "{$block_id}:{$field['name']}", $value ); $values[ $field['name'] ] = $value; $values[ '_' . $field['name'] ] = $field['key']; // TODO: Is there a better way to generate this? } $block['data'] = $values; acf_set_data( $block_id . '_loaded_meta_values', true ); return $block; } /** * Stores ACF field values in post meta for any blocks configured to do so. * * @since 6.3 * * @param integer $post_id The ID of the post being saved. * @param WP_Post $post The post object. * @return void */ function acf_save_block_meta_values( $post_id, $post ) { $meta_values = acf_get_block_meta_values_to_save( $post->post_content ); if ( empty( $meta_values ) ) { return; } // Save values for any post meta blocks. acf_save_post( $post_id, $meta_values ); } add_action( 'save_post', 'acf_save_block_meta_values', 10, 2 ); /** * Iterates over blocks in post content and retrieves values * that need to be saved to post meta. * * @since 6.3 * * @param string $content The content saved for the post. * @return array An array containing the field values that need to be saved. */ function acf_get_block_meta_values_to_save( $content = '' ) { $meta_values = array(); // Bail early if not in a format we expect or if it has no blocks. if ( ! is_string( $content ) || empty( $content ) || ! has_blocks( $content ) ) { return $meta_values; } $blocks = parse_blocks( $content ); // Bail if no blocks to save. if ( ! is_array( $blocks ) || empty( $blocks ) ) { return $meta_values; } foreach ( $blocks as $block ) { // Verify this is an ACF block that should save to meta. if ( ! acf_block_uses_post_meta( $block['attrs'] ) ) { continue; } // We need a block ID to retrieve the values from cache. $block_id = ! empty( $block['attrs']['id'] ) ? $block['attrs']['id'] : false; if ( ! $block_id ) { continue; } // Verify that we have values for this block. $store = acf_get_store( 'block-meta-values' ); if ( ! $store->has( $block_id ) ) { continue; } // Get the values and remove from cache. $block_values = $store->get( $block_id ); $store->remove( $block_id ); $meta_values = array_merge( $meta_values, $block_values ); } return $meta_values; } /** * Helper function that returns the HTML attributes required for toolbar inline editing as a string or array. * * @param array $fields Array { * Required. A list of the fields, each of which which will be displayed in the popup toolbar. * * Each field can be passed as: * * - A string (e.g. `'my_field_name'`) * - An associative array with specific keys: * @type string $field_name The name of the field to display in the toolbar. * @type string $field_icon An html tag, can be an svg, to be used as the toolbar icon. If not passed, the icon of the first field will be used. * @type string $field_label A string to use as the label for the button in the toolbar. * @type boolean $use_expanded_editor Default is false, which opens the field in the popover. Set to true to open in the expanded editor. * @type string $popover_min_width Enter the CSS width value to use for the popover. Default is "300px". * } * * @param array $args Array { * Optional. An array of additional args which can control how the toolbar is displayed and used. * * @type string $toolbar_icon Optional. An html tag, can be an svg, to be used as the toolbar icon. If not passed, the icon of the first field will be used. * @type string $toolbar_title Optional. A string to be used as the toolbar title. If not passed, the name of the first field will be used. * @type string $uid Optional. A unique identifier that isn't used by any other inline fields in this block. Pass if you have 2 elements that conflict. * @type boolean $return_array Optional. If true, returns an array of attributes suitable for wp_get_attachment_image(). Default false. * } * * @return string|array When $args['return_array'] is false (default): Returns a string of escaped HTML attributes ready for output. * When $args['return_array'] is true: Returns an associative array of attribute names and escaped values. * When using the array return value with wp_get_attachment_image(), no element will be rendered if * the image field is empty. If users need an inline editing target for selecting an image, render a fallback element * with the attributes returned by this function. */ function acf_inline_toolbar_editing_attrs( $fields, $args = array() ) { $default_args = array( 'toolbar_icon' => null, 'toolbar_title' => null, 'uid' => null, 'return_array' => false, ); $args = wp_parse_args( $args, $default_args ); // Helper for consistent early returns based on return format $empty_return = $args['return_array'] ? array() : ''; if ( empty( $fields ) ) { return $empty_return; } $acf_block_version = acf_get_data( 'acf_current_block_version' ); if ( ! $acf_block_version || $acf_block_version <= 2 ) { return $empty_return; } $render = acf_get_data( 'acf_doing_block_preview' ); if ( ! $render ) { return $empty_return; } // Get the block id. $meta_instance = acf_get_instance( 'ACF_Local_Meta' ); $block_id = $meta_instance->post_id; if ( empty( $block_id ) || strpos( $block_id, 'block_' ) !== 0 ) { return $empty_return; } // Prefix the generated uid with the blockid. $generated_uid = $block_id; $fields_processed = array(); /** * Filters the field types that should open in the expanded editor by default. * * @since 6.7.0 * * @param array $field_types An array of field type names. * @return array */ $fields_to_open_in_expanded_editor = apply_filters( 'acf/blocks/fields_to_open_in_expanded_editor', array( 'repeater', 'flexible_content', ) ); /** * Filters the field types that require a wider popover for inline editing. * * @since 6.7.0 * * @param array $field_types An array of field type names. * @return array */ $fields_needing_wide_popover = apply_filters( 'acf/blocks/fields_needing_wide_popover', array( 'gallery', 'relationship', 'wysiwyg', 'google_map', ) ); $popover_min_width_normal = '300px'; $popover_min_width_wide = '600px'; foreach ( $fields as $field ) { if ( is_array( $field ) ) { $full_field_data = acf_get_field( $field['field_name'] ); if ( $full_field_data ) { $use_expanded_editor_default = in_array( $full_field_data['type'], $fields_to_open_in_expanded_editor, true ) ? true : false; $popover_min_width_default = in_array( $full_field_data['type'], $fields_needing_wide_popover, true ) ? $popover_min_width_wide : $popover_min_width_normal; $generated_uid .= $field['field_name']; $fields_processed[] = array( 'fieldName' => $field['field_name'], // Base64 allows us to embed html in an attribute without causing issues with quotes, etc. 'fieldIcon' => ! empty( $field['field_icon'] ) ? base64_encode( $field['field_icon'] ) : null, // phpcs:ignore 'fieldLabel' => ! empty( $field['field_label'] ) ? $field['field_label'] : $full_field_data['label'], 'useExpandedEditor' => ! empty( $field['use_expanded_editor'] ) ? $field['use_expanded_editor'] : $use_expanded_editor_default, 'popoverMinWidth' => ! empty( $field['popover_min_width'] ) ? $field['popover_min_width'] : $popover_min_width_default, ); } } else { $full_field_data = acf_get_field( $field ); if ( $full_field_data ) { $fields_processed[] = array( 'fieldName' => $field, 'fieldIcon' => null, 'fieldLabel' => $full_field_data['label'], 'useExpandedEditor' => in_array( $full_field_data['type'], $fields_to_open_in_expanded_editor, true ) ? true : false, 'popoverMinWidth' => in_array( $full_field_data['type'], $fields_needing_wide_popover, true ) ? $popover_min_width_wide : $popover_min_width_normal, ); } $generated_uid .= $field; } } if ( empty( $args['uid'] ) ) { $args['uid'] = $generated_uid; } // Build the attributes array. $attributes = array( 'data-acf-inline-fields-uid' => $args['uid'], 'data-acf-inline-fields' => wp_json_encode( $fields_processed ), 'role' => 'button', 'tabindex' => '0', ); if ( ! empty( $args['toolbar_icon'] ) ) { $attributes['data-acf-toolbar-icon'] = $args['toolbar_icon']; } if ( ! empty( $args['toolbar_title'] ) ) { $attributes['data-acf-toolbar-title'] = $args['toolbar_title']; } // Return as array if requested. if ( $args['return_array'] ) { return array_map( 'esc_attr', $attributes ); } // Build and return as string (default behavior). $output = ''; foreach ( $attributes as $key => $value ) { $output .= $key . '="' . esc_attr( $value ) . '" '; } return rtrim( $output ); } /** * Helper function that returns the HTML attributes required for inline text editing as a string, escaped and ready for output. * * @param string $field_name A string which is the name of the field to update when the user types into the HTML element. * * @param array $args Array { * Optional. An array of additional args which can control how the popover identifier is displayed. * * @type string $toolbar_icon Optional. An html tag, can be an svg, to be used as the toolbar icon. If not passed, the icon of the first field will be used. * @type string $toolbar_title Optional. A string to be used as the toolbar title. If not passed, the name of the first field will be used. * @type string $placeholder Optional. Optional. A string which will be used as the placeholder in the typable text area. * } * * @return string A string containing the attributes. */ function acf_inline_text_editing_attrs( $field_name, $args = array() ): string { if ( empty( $field_name ) ) { return ''; } $acf_block_version = acf_get_data( 'acf_current_block_version' ); if ( ! $acf_block_version || $acf_block_version <= 2 ) { return ''; } $default_args = array( 'toolbar_icon' => null, 'toolbar_title' => null, 'placeholder' => null, ); $args = wp_parse_args( $args, $default_args ); $render = acf_get_data( 'acf_doing_block_preview' ); if ( ! $render ) { return ''; } if ( ! empty( $args['toolbar_icon'] ) ) { $args['toolbar_icon'] = 'data-acf-toolbar-icon="' . esc_attr( $args['toolbar_icon'] ) . '" '; } if ( ! empty( $args['toolbar_title'] ) ) { $args['toolbar_title'] = 'data-acf-toolbar-title="' . esc_attr( $args['toolbar_title'] ) . '" '; } if ( ! $args['placeholder'] ) { $args['placeholder'] = __( 'Type to edit...', 'acf' ); } return 'data-acf-inline-contenteditable="1" data-acf-inline-contenteditable-field-slug="' . esc_attr( $field_name ) . '" data-acf-placeholder="' . esc_attr( $args['placeholder'] ) . '" ' . $args['toolbar_icon'] . $args['toolbar_title']; } /** * This function prepares a fields array for being localized and used on the frontend as block toolbar fields. * * @param array $fields Array { * Required. A list of the fields, each of which which will be displayed in the popup toolbar. * * Each field can be passed as: * * - A string (e.g. `'my_field_name'`) * - An associative array with specific keys: * @type string $field_name The name of the field to display in the toolbar. * @type string $field_icon An html tag, can be an svg, to be used as the toolbar icon. If not passed, the icon of the first field will be used. * @type string $field_label A string to use as the label for the button in the toolbar. * } * * @return array The array of fields, prepared for JS localization. */ function acf_process_block_toolbar_fields( $fields ) { $fields_processed = array(); foreach ( $fields as $index => $field ) { if ( is_array( $field ) ) { $fields_processed[] = array( 'fieldName' => ! empty( $field['field_name'] ) ? $field['field_name'] : $index, // Base64 allows us to embed html in an attribute without causing issues with quotes, etc. 'fieldIcon' => ! empty( $field['field_icon'] ) ? base64_encode( $field['field_icon'] ) : null, // phpcs:ignore 'fieldLabel' => ! empty( $field['field_label'] ) ? $field['field_label'] : null, ); } else { $fields_processed[] = $field; } } return $fields_processed; } /** * Helper function for block render templates to check if an acf field has a value. * This is relevant when autoInlineEditing is enabled for a block, because empty fields * will have acf_auto_inline_editing_field_name_ + field_name as their value if they are empty. * * @param string $field_name True if the field is empty, false if it has a value. * @return boolean True if the field is empty, false if it has a value. */ function acf_inline_editing_field_is_empty( $field_name ) { $field_value = get_field( $field_name ); if ( empty( $field_value ) || $field_value === 'acf_auto_inline_editing_field_name_' . $field_name ) { return true; } return false; }